Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 7 Weitere Möglichkeiten von C#
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladungsbeispiele
    gp 7.1.4 Benutzerdefinierte Konvertierungen – implizit und explizit
  gp 7.2 Indexer
    gp 7.2.1 Überladen von Indexern
    gp 7.2.2 Parameterbehaftete Eigenschaften
  gp 7.3 Collections (Auflistungen)
    gp 7.3.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.3.2 Die Klasse »ArrayList«
    gp 7.3.3 Das Sortieren der Elemente einer »ArrayList«
    gp 7.3.4 Die Schnittstelle »IDictionary«
    gp 7.3.5 Die Klasse »Hashtable«
    gp 7.3.6 Die Klassen »Queue« und »Stack«
    gp 7.3.7 Objektauflistungen im Überblick
    gp 7.3.8 Benutzerdefinierte Auflistungen
  gp 7.4 Generics – Generische Datentypen
    gp 7.4.1 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.4.2 Die Lösung mit einer generischen Klasse
    gp 7.4.3 Typparameter mit Constraints einschränken
    gp 7.4.4 Generische Methoden
    gp 7.4.5 Generics und Vererbung
    gp 7.4.6 Konvertierung von Generics
    gp 7.4.7 Generische Delegate
    gp 7.4.8 Generische Klassen in der .NET-Klassenbibliothek
    gp 7.4.9 Eigene Auflistungen mit »yield« durchlaufen
    gp 7.4.10 Daten durch »null« beschreiben
  gp 7.5 Fortgeschrittene Delegat-Techniken
    gp 7.5.1 Multicast-Delegate
  gp 7.6 Attribute
    gp 7.6.1 Das »Flags«-Attribut
    gp 7.6.2 Anmerkungen zu den Attributen
    gp 7.6.3 Benutzerdefinierte Attribute
  gp 7.7 Unsicherer Programmcode – Zeigertechnik in C#
    gp 7.7.1 Das Schlüsselwort »unsafe«
    gp 7.7.2 Die Deklaration von Zeigern
    gp 7.7.3 Die »fixed«-Anweisung
    gp 7.7.4 Zeigerarithmetik
    gp 7.7.5 Der Operator »->«

Kapitel 7 Weitere Möglichkeiten von C#


Galileo Computing

7.1 Operatorüberladung  downtop

C# verfügt über eine Reihe von Operatoren, die Sie für allgemeine Operationen einsetzen können. Werden zwei Zahlen dividiert, müssen Sie sich keine darüber Gedanken machen, welcher Code im Hintergrund vom Compiler erzeugt wird:


double ergebnis = var1 / var2;

Die Frage nach dem Typ der Operanden ist nicht bedeutungslos. Handelt es sich um ganzzahlige Typen, wird ein anderes Kompilat erzeugt als bei zwei Dezimalzahlen. Abhängig vom Typ der Operanden werden zwei unterschiedliche Operationen ausgeführt. Der Compiler entscheidet darüber, um welche Operation es sich dabei handelt, denn der »/«-Operator ist überladen.

Insbesondere für die elementaren Datentypen sind die meisten Operatoren überladen. Sie führen, je nach Typ der Operanden, unterschiedliche Operationen aus. Eine der großen Stärken von C# ist, dass dem Entwickler das Instrumentarium in die Hand gegeben wird, im Bedarfsfall Operatoren benutzerdefiniert zu überladen.


Galileo Computing

7.1.1 Die Syntax der Operatorüberladung  downtop

Um Operatoren in einer Klasse oder einer Struktur zu überladen, stellt C# das Schlüsselwort operator zur Verfügung, das nur in Verbindung mit public static verwendet werden darf. Hinter dem operator-Schlüsselwort wird der Operator angegeben, der überladen werden soll. Die folgende Syntax gilt für binäre Operatoren, die zwei Operanden für ihre Operation benötigen.


public static Ergebnistyp operator Operator (Operand1, Operand2)

Neben den binären gibt es auch unäre Operatoren, die nur einen Operanden verlangen. Stellvertretend seien hier die Operatoren »++« und »--« genannt. Für diese Operatorengruppe ändert sich die Syntax wie folgt:


public static Ergebnistyp operator Operator (Operand)

Wenn Sie eine Klasse um Methoden zur Operatorüberladung erweitern, sollten Sie folgende Punkte berücksichtigen:

gp  Es können nur bereits vordefinierte Operatoren überladen werden. Sie können also keine neuen Operatoren »erfinden«.
gp  Die Operationen von Operatoren auf den systemeigenen Typen können nicht umdefiniert werden.
gp  Die Grundfunktionalität eines Operators bleibt immer erhalten: Ein binärer Operator benötigt immer zwei Operanden, ein unärer immer einen. Die Vorrangregeln können nicht beeinflusst werden.

In der folgenden Tabelle sind alle Operatoren aufgeführt, die in einer Klasse oder Struktur überladen werden dürfen.


Tabelle 7.1   Überladbare Operatoren

C#-Operator Bedeutung
+, –, !, ~, ++, --, true, false Unäre Operatoren
+, –, *, /, %, &, |, ^, <<, >> Binäre Operatoren
==, !=, <, >, <=, >= Relationale Operatoren
[] Dieser Operator kann eigentlich nicht überladen werden. Es gibt jedoch ein Ersatzkonstrukt (Indexer), das die gleiche Funktionalität bietet (siehe Abschnitt 6.2).

Einige Operatoren können nur paarweise überladen werden. Wollen Sie zum Beispiel den Vergleichsoperator »==« überladen, müssen Sie auch den Operator »!=« überladen. Damit erzwingt C# eine konsistente Prüfung auf Übereinstimmung und Nichtübereinstimmung.

Einschränkungen der Operatorüberladung

Nicht alle Operatoren sind überladungsfähig. So können Sie den Zuweisungsoperator »=« nicht überladen. Überladen Sie einen binären Operator, z.B. »+«, wird der Additionszuweisungsoperator »+=« automatisch implizit überladen.

Zu den anderen nichtüberladbaren Operatoren gehören der Punktoperator, der bedingte Operator »?:« sowie die Operatoren new, is, typeof und sizeof. Ebenso wenig überladbar sind die runden Klammern, mit denen eine Typkonvertierung durchgeführt wird. Stattdessen sollten benutzerdefinierte Konvertierungen implementiert werden. Dieses Thema beschäftigt uns weiter unten.


Galileo Computing

7.1.2 Beispiel einer Operatorüberladung  downtop

Wir wollen uns die Operatorüberladung jetzt an einem Beispiel ansehen. Dazu rufen wir uns die Methode IsBigger der Klasse GeometricObject in Erinnerung:


public static bool IsBigger(GeometricObject geoObj1, GeometricObject geoObj2) {
  if(geoObj1.GetFlaeche() >= geoObj2.GetFlaeche())
    return true;
  return false;
}

Mit


Circle kreis1 = new Circle(2);
Circle kreis2 = new Circle(3);
if(GeometricObject.IsBigger(kreis1, kreis2)) {
   // Anweisung
}

können wir zweifelsfrei prüfen, welcher der beiden Kreise der größere oder der kleinere ist. Selbstkritisch müssen wir aber auch feststellen, dass der Ausdruck


if(kreis1 >= kreis2)

eher einer üblichen Vergleichsoperation entspricht. Bisher ist diese Vergleichsoperation jedoch nicht möglich, weil sie für Objekte vom Typ der Basisklasse GeometricObject oder in einer der abgeleiteten Klassen nicht definiert ist.

Um dieses Defizit auszugleichen, wollen wir jetzt den »>=«-Operator so überladen, dass er zur Laufzeit auf zwei Objekte vom Typ GeometricObject angewendet werden kann:


// Operatorüberladung
public static bool operator >=(GeometricObject geoObj1, GeometricObject geoObj2) {
  if(geoObj1.GetFlaeche() >= geoObj2.GetFlaeche())
    return true;
  return false;
}

Kompilieren wir die so ergänzte Klassendefinition, werden wir einen Compilerfehler erhalten, weil sich ein GeometricObject-Objekt jetzt nicht mehr eindeutig verhält. Wir werden gezwungen, einen weiteren Vergleichsoperator zu überladen, nämlich den, der die Umkehrung der vorher überladenen Vergleichsfunktion beschreibt.


public static bool operator <=(GeometricObject geoObj1, GeometricObject geoObj2) {
  if(geoObj1.GetFlaeche() <= geoObj2.GetFlaeche())
    return true;
  return false;
}

Nach dem anschließenden, jetzt erfolgreichen Kompilieren können wir mit


Circle kreis1 = new Circle(6);
Circle kreis2 = new Circle(3);
if(kreis1 >= kreis2) {
  // Anweisungen
}

alternativ zu der von uns implementierten Methode IsBigger Vergleichsoperationen mit Objekten unserer Klasse ausführen.


Wird aus einer Klasse, die Operatoren überlädt, eine weitere Klasse abgeleitet, vererben sich die überladenen Operatoren an die abgeleitete Klasse.


Galileo Computing

7.1.3 Überladungsbeispiele  downtop

Überladen von Gleichheitsoperatoren

In Kapitel 4, Abschnitt 4.7.6, haben Sie erfahren, wie mit der Methode Equals der Klasse Object zwei Referenzen auf Gleichheit untersucht werden können:


Circle kreis1 = new Circle(12);
Circle kreis2 = kreis1;
if(kreis1.Equals(kreis2))
  Console.WriteLine("referenzielle Gleichheit");
else
  Console.WriteLine("zwei verschiedene Objekte");

Zeigen beide Referenzen auf dasselbe Objekt, liefert die Equals-Methode als Rückgabewert true. Das ist das Standardverhalten dieser Methode. In gleicher Weise arbeitet auch der »==«-Vergleichsoperator. Wir könnten demnach die if-Anweisung auch wie folgt formulieren:


if(kreis1 == kreis2)
  // Anweisungen

Die Instanzmethode Equals (es gibt auch eine statische Klassenmethode gleichen Namens) ist virtuell definiert und kann von jeder Klasse überschrieben werden, beispielsweise um anstatt eines Referenzvergleichs einen wertbasierten Vergleich der Zustandsdaten von zwei typgleichen Objekten zu erzwingen. Wenn Equals überschrieben wird, sollte auch der Vergleichsoperator der benutzerdefinierten Klasse überschrieben werden, damit die Austauschbarkeit zwischen Equals und dem »==«-Operator weiterhin gewährleistet bleibt.

In der Klasse GeometricObject wird nun die Equals-Methode so überschrieben, dass der Vergleich zweier Objekte nicht mehr anhand der Referenz erfolgt, sondern dazu die Fläche der Objekte ausgewertet wird.


class GeometricObject {
  ...
  public static bool operator==(GeometricObject geo1, GeometricObject geo2) {
    return geo1.Equals(geo2);
  }
  public static bool operator!=(GeometricObject geo1, GeometricObject geo2) {
    return !geo1.Equals(geo2);
  }
  // überschriebene Methode Equals
  public override bool Equals(object obj) {
    if(this.GetFlaeche() == ((GeometricObject)obj).GetFlaeche())
      return true;
    return false;
  }
}

Die Implementierung der beiden operator-Methoden ist denkbar einfach: Es wird nur die in der Klasse überschriebene Methode Equals aufgerufen, welche die eigentliche Arbeit verrichtet.


Hinweis   Dass mit der Überschreibung von Equals auch die von Object geerbte Methode GetHashCode überschrieben werden sollte, ignorieren wir in diesem Beispiel. Wenn Sie mehr über die Methode GetHashCode wissen möchten, lesen Sie bitte in Kapitel 10 nach.

»true- und false«-Operatoren

Wenn Sie Tabelle 7.1 aufmerksam studiert haben, werden Ihnen vermutlich zwei ungewöhnlich erscheinende, überladungsfähige Operatoren aufgefallen sein: true und false. Diese dienen dazu, Operationen wie beispielsweise


if (myObject)
  ...

zu ermöglichen. Diese Bedingungsprüfung ist sinnvoll, wenn der Rückgabewert direkt abhängig von einem Feld ist. Soll außerdem auch noch der Negationsoperator berücksichtigt werden, muss auch der »!«-Operator überladen werden.


if(!myObject)
  ...

Die Operatoren true und false gehören ebenfalls zu der Gruppe der Operatoren, die man nur paarweise überladen kann. Die Rückgabe ist ein boolescher Wert. Im folgenden Beispiel wird die Überladung aller drei Operatoren gezeigt. Dazu wird festgelegt, dass ein Objekt dann true zu bewerten ist, wenn der Inhalt des objektspezifischen ungleich der Zahl 0 ist.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 7\True_False_Operator
//---------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    MyClass myObj = new MyClass();
    myObj.intValue = 8;
    if(myObj)
      Console.Write("Wert ungleich 0");
    else
      Console.Write("Wert gleich 0");
    Console.ReadLine();
  }
}
// ---------------- MyClass-Definition ------------------
class MyClass {
  public int intValue = 0;
  // Überladung des true-Operators
  public static bool operator true(MyClass obj) {
    if(obj.intValue != 0)
      return true;
    return false;
  }
  // Überladung des false-Operators
  public static bool operator false(MyClass obj) {
    if(obj.intValue != 0)
      return false;
    return true;
  }
  // Überladung des Negationsoperators
  public static bool operator !(MyClass obj) {
    if(obj.intValue != 0)
      return false;
    return true;
  }
}

Die dem Feld zugewiesene Zahl 8 wird mit


if (myObj)

zu der Anzeige


Wert ungleich 0

führen. Benutzen wir im Ausdruck den »!«-Operator, kehrt sich die Logik natürlich um und führt zu der Ausgabe:


Wert gleich 0


Galileo Computing

7.1.4 Benutzerdefinierte Konvertierungen – implizit und explizit  toptop

Stellen Sie sich vor, Sie hätten die Klasse ClassA folgendermaßen definiert:


class ClassA {
  public int MyValue = 0;
}

Zugegeben, die Implementierung ist äußerst trivial, denn der Typ ClassA enthält nur ein int-Feld. Diese Definition könnte dazu verleiten, eine Referenz der Klasse ClassA einer int-Variablen wie folgt zuzuweisen:


ClassA obj = new ClassA();
obj.MyValue = 1;
int x = obj;

Selbstverständlich wird es nur bei einem Versuch bleiben, denn der Compiler stellt eine unzulässige Konvertierung des ClassA-Typs in einen int fest und macht das Unterfangen zunichte.

C# bietet uns die Möglichkeit, bestimmte Typkonvertierungen zu gestatten. Angenommen unser Ziel sei, das Codefragment tatsächlich einwandfrei zu kompilieren. Dazu müssen wir die Klasse um die Definition einer benutzerdefinierten Konvertierung wie folgt erweitern:


class ClassA {
  public int MyValue = 0;
  public static implicit operator int(ClassA obj) {
   return obj.MyValue;
  }
}

Sehen wir uns den Methodenkopf genauer an. Im Vergleich zu einer Methode, die einen Operator überlädt, ist die Definition der Methode zur Typkonvertierung um das neue Schlüsselwort implicit ergänzt. Den Schlüsselwörtern implicit operator folgt der Datentyp, in den implizit konvertiert wird. In unserem Beispiel ist es int. Der Parameter definiert den Typ, der konvertiert werden soll.

Die allgemeine Syntax der impliziten benutzerdefinierten Typkonvertierung lautet:


public static implicit operator Zieldatentyp(Eingabetyp)

Die Aussage in unserem Beispiel ist also die folgende: Konvertiere ein Objekt vom Typ ClassA implizit in einen int.

Benutzerdefinierte Konvertierungen liefern ein Ergebnis: Es ist genau von dem Typ, der hinter operator angegeben wird. Im Anweisungsblock der Konvertierungsmethode muss deshalb ein return-Statement gefolgt vom entsprechenden Rückgabewert angegeben werden – in unserem Fall der Inhalt des Feldes MyValue des Objekts, dessen Referenz die Methode im Parameter obj empfängt.

Weil ein int vom System implizit in einen long-Typ konvertiert wird, wird jetzt auch das folgende Codefragment fehlerfrei kompiliert:


ClassA a = new ClassA();
a.MyValue = 1;
long x = a;

Eine implizite Konvertierung kann auch zwischen Klassen erzwungen werden, die sich auf keiner Vererbungslinie befinden. Im folgenden Beispielprogramm wird das gezeigt. Es definiert die beiden Klassen ClassA und ClassB. In ClassA wird eine implizite Konvertierung in ClassB ermöglicht.

Eine implizite Konvertierung bietet sich an, wenn bei einer Konvertierung keine Daten verloren gehen. Nehmen wir nun an, die Klasse ClassA sei etwas anspruchsvoller:


class ClassA {
  public int MyValue = 0;
  public string MyText = "Prima C#";
}

Wir wollen wieder sicherstellen, die Referenz auf ein ClassA-Objekt in einen Integer zu konvertieren. Es kann uns nichts davon abhalten, weiterhin eine implizite benutzerdefinierte Konvertierung anzubieten. Tatsache ist aber, dass uns bei der Typumwandlung Informationen verloren gehen, auch wenn diese vom empfangenden Element nicht benötigt werden – hier ist es das Feld vom Typ string. Legen Sie Wert auf eine stilistisch saubere Programmierung, sollten Sie deshalb eine explizite Konvertierung vorschreiben. Sie vermeiden dadurch außerdem, dass eine implizite Konvertierung automatisch ausgeführt wird, ohne dass der Aufrufer sie gewünscht hat.

Um eine benutzerdefinierte, explizite Typumwandlung zu implementieren, muss das Schlüsselwort explicit in der Methodensignatur angegeben werden. Die allgemeine Syntax ähnelt der impliziten benutzerdefinierten Konvertierung:


public static explicit operator Zieldatentyp(Eingabedatentyp)

Sehen wir uns dazu das vollständige Beispiel an:


class ClassA {
  public int MyValue = 0;
  public string MyText = "Hallo Welt";
  public static explicit operator int(ClassA obj) {
    return obj.MyValue;
  }
  public static explicit operator string(ClassA obj) {
    return obj.MyText;
  }
}

ClassA beschreibt nun sogar zwei explizite Konvertierungen: in einen int und in einen string. Programmcode, der ein ClassA-Objekt einem int zuweisen möchte, würde jetzt zu einer expliziten Konvertierung gezwungen, z.B.:


ClassA a = new ClassA();
a.MyValue = 13;
int x = (int)a;

Analog lässt sich mit der Anweisung


string str = (string)a;

eine Referenz vom Typ ClassA auch einer string-Variablen zuweisen.

Implizite und explizite Konvertierung von Referenztypen

In den beiden vorhergehenden Abschnitten war der Zieldatentyp ein einfacher Datentyp. Auf dieselbe Art und Weise kann auch in einen Referenztyp konvertiert werden. Nehmen wir an, es wären die beiden Klassen ClassA und ClassB wie folgt definiert:


class ClassA {
  public int MyValue;
  public bool MyBol;
}
class ClassB {
  public int Value;
  public long LngValue;
}

Unser Ziel sei uns nun, die implizite Konvertierung eines ClassA-Objekts in ein ClassB-Objekt und umgekehrt zu erreichen, um beispielsweise den folgenden Code zu ermöglichen:


ClassA objA = new ClassA();
objA.MyValue = 4722;
ClassB objB = objA;

Würden die beiden Klassen in einer Vererbungsbeziehung stehen und ClassA aus ClassB abgeleitet, wäre die Zuweisung kein Problem, da eine Subklassenreferenz implizit in eine Basisklassenreferenz konvertiert wird. Wie wir wissen, können wir auch die Referenz eines Basisklassenobjekts einer Subklassenreferenz zuweisen, müssen aber die Basisklassenreferenz dabei explizit konvertieren. Der passende Konvertierungsoperator wird uns aufgrund der Vererbungsbeziehung automatisch zur Verfügung gestellt. Die Ausgangssituation zwischen ClassA und ClassB ist in unserem Beispiel eine gänzlich andere und zwingt uns zu einer benutzerdefinierten Konvertierung, entweder implizit oder explizit.

Der Zieldatentyp ist nun ClassB, d. h., dass die Konvertierungsfunktion ein Objekt vom Typ ClassB als Rückgabewert liefert. In der Routine wird deshalb ein neues Objekt dieses Typs erzeugt. Man muss sich jetzt noch überlegen, welche gemeinsamen Merkmale beide Klassen aufweisen. In unserem Beispiel handelt es sich nur um das in beiden Klassen deklarierte Feld vom Typ int. Daher wird der Inhalt des Feldes MyValue des ClassA-Objekts dem Feld Value des ClassB-Objekts zugewiesen. Das so initialisierte Objekt wird als Ergebnis der impliziten Konvertierung bereitgestellt.


// implizite Konvertierung eines ClassA- in ein ClassB-Objekt
public static implicit operator ClassB(ClassA obj) {
  ClassB bObj = new ClassB();
  bObj.Value = obj.MyValue;
  return bObj;
}

Die Konvertierung eines ClassA- in ein ClassB-Objekt hat Datenverlust zur Folge. Daher ist es besser, anstelle der impliziten eine explizite Konvertierung vorzuschreiben. Dazu muss nur das Schlüsselwort implicit gegen explicit ausgetauscht werden:


// explizite Konvertierung eines ClassA- in ein ClassB-Objekt
public static explicit operator ClassB(ClassA obj) {
  ClassB bObj = new ClassB();
  bObj.Value = obj.MyValue;
  return bObj;
}

Nun wird die Typumwandlung nur über den Konvertierungsoperator erlaubt, führt aber ansonsten zum gleichen Ergebnis wie eine implizite Konvertierung:


ClassA objA = new ClassA();
objA.MyValue = 55;
ClassB objB = (ClassB)objA;

Auf der Buch-CD finden Sie die Beispielprogramme ConvertImplicitDemo und ConvertExplicitDemo, welche die implizite und explizite Konvertierung zeigen.

Überladen benutzerdefinierter Konvertierungsmethoden

Das gleichzeitige Implementieren einer expliziten und einer impliziten Konvertierungsfunktion mit demselben Zieldatentyp ist nicht zulässig. Daher ist der folgende Programmcode falsch:


// fehlerhafte Klassendefinition aufgrund unzulässiger 
// Überladung der benutzerdefinierten Konvertierung
class ClassA {
  public int MyValue = 0;
  public static implicit operator int(ClassA obj) {
    return obj.MyValue;
  }
  public static explicit operator int(ClassA obj) {
    return obj.MyValue;
  }
}

Gestattet ist jedoch die Koexistenz einer impliziten und einer expliziten Konvertierung, wenn sich die beiden Überladungsmethoden im Zieldatentyp unterscheiden. Im folgenden Codefragment ist in ClassA eine explizite Konvertierung in den Typ long definiert und eine implizite in einen int. Die Klassendefinition wird fehlerfrei kompiliert.


// zulässige Überladung der Konvertierungsmethode  
class ClassA {
  public int MyValue = 0;
  public static implicit operator int(ClassA obj) {
    return obj.MyValue;
  }
  public static explicit operator long(ClassA obj) {
    return obj.MyValue;
  }
}

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de